#include "stdafx.h"
#include "filesys/glob.hpp"
#include "filesys/path.hpp"
#include "filesys/directoryiterator.hpp"
#include "charset/unicode.hpp"
#include "charset/textencode.hpp"

namespace JHCPP
{
	namespace filesys
	{
		CGlob::CGlob(const std::string& pattern, int options) : m_pattern(pattern), m_options(options)
		{
		}

		CGlob::~CGlob()
		{
		}

		bool CGlob::match(const std::string& subject)
		{
			CUTF8Encode utf8;
			CTextIterator itp(m_pattern, utf8);
			CTextIterator endp(m_pattern);
			CTextIterator its(subject, utf8);
			CTextIterator ends(subject);

			if ((m_options & GLOB_DOT_SPECIAL) && its != ends && *its == '.' && (*itp == '?' || *itp == '*'))
				return false;
			else
				return match(itp, endp, its, ends);
		}

		void CGlob::glob(const CPath& pathPattern, std::set<std::string>& files, int options)
		{
			CPath pattern(pathPattern);
			pattern.makeDirectory(); // to simplify pattern handling later on
			CPath base(pattern);
			CPath absBase(base);
			absBase.makeAbsolute();
			// In case of UNC paths we must not pop the topmost directory
			// (which must not contain wildcards), otherwise collect() will fail
			// as one cannot create a DirectoryIterator with only a node name ("\\srv\").
			int minDepth = base.getNode().empty() ? 0 : 1;
			while (base.depth() > minDepth && base[base.depth() - 1] != "..") 
			{
				base.popDirectory();
				absBase.popDirectory();
			}
			if (pathPattern.isDirectory()) options |= GLOB_DIRS_ONLY;
			collect(pattern, absBase, base, pathPattern[base.depth()], files, options);		
		}

		bool CGlob::match(CTextIterator& itp, const CTextIterator& endp, CTextIterator& its, const CTextIterator& ends)
		{
			while (itp != endp)
			{
				if (its == ends)
				{
					while (itp != endp && *itp == '*') ++itp;
					break;
				}
				switch (*itp)
				{
				case '?':
					++itp; ++its;
					break;
				case '*':
					if (++itp != endp)
					{
						while (its != ends && !matchAfterAsterisk(itp, endp, its, ends)) ++its;
						return its != ends;
					}
					return true;
				case '[':
					if (++itp != endp) 
					{
						bool invert = *itp == '!';
						if (invert) ++itp;
						if (itp != endp)
						{
							bool mtch = matchSet(itp, endp, *its++);
							if ((invert && mtch) || (!invert && !mtch)) return false;
							break;
						}
					}
					throw SyntaxException("bad range syntax in glob pattern");
				case '\\':
					if (++itp == endp) throw SyntaxException("backslash must be followed by character in glob pattern");
					// fallthrough
				default:
					if (m_options & GLOB_CASELESS)
					{
						if (CUnicode::toLower(*itp) != CUnicode::toLower(*its)) return false;
					}
					else
					{
						if (*itp != *its) return false;
					}
					++itp; ++its;
				}
			}
			return itp == endp && its == ends;
		}

		bool CGlob::matchAfterAsterisk(CTextIterator itp, const CTextIterator& endp, CTextIterator its, const CTextIterator& ends)
		{
			return match(itp, endp, its, ends);
		}

		bool CGlob::matchSet(CTextIterator& itp, const CTextIterator& endp, int c)
		{
			if (m_options & GLOB_CASELESS)
				c = CUnicode::toLower(c);

			while (itp != endp)
			{
				switch (*itp)
				{
				case ']':
					++itp; 
					return false;
				case '\\':
					if (++itp == endp) throw SyntaxException("backslash must be followed by character in glob pattern");
				}
				int first = *itp;
				int last  = first;
				if (++itp != endp && *itp == '-')
				{
					if (++itp != endp)
						last = *itp++;
					else
						throw SyntaxException("bad range syntax in glob pattern");
				}
				if (m_options & GLOB_CASELESS)
				{
					first = CUnicode::toLower(first);
					last  = CUnicode::toLower(last);
				}
				if (first <= c && c <= last)
				{
					while (itp != endp)
					{
						switch (*itp)
						{
						case ']':
							++itp;
							return true;
						case '\\':
							if (++itp == endp) break;
						default:
							++itp;
						}
					}
					throw SyntaxException("range must be terminated by closing bracket in glob pattern");
				}
			}
			return false;
		}

		void CGlob::collect(const CPath& pathPattern, const CPath& base, const CPath& current, const std::string& pattern, std::set<std::string>& files, int options)
		{
			try
			{
				std::string pp = pathPattern.toString();
				std::string basep = base.toString();
				std::string curp  = current.toString();
				CGlob g(pattern, options);
				CDirectoryIterator it(base);
				CDirectoryIterator end;
				while (it != end)
				{
					const std::string& name = it.name();
					if (g.match(name))
					{
						CPath p(current);
						if (p.depth() < pathPattern.depth() - 1)
						{
							p.pushDirectory(name);
							collect(pathPattern, it.path(), p, pathPattern[p.depth()], files, options);
						}
						else
						{
							p.setFileName(name);
							if (isDirectory(p, (options & GLOB_FOLLOW_SYMLINKS) != 0))
							{
								p.makeDirectory();
								files.insert(p.toString());
							}
							else if (!(options & GLOB_DIRS_ONLY))
							{
								files.insert(p.toString());
							}
						}
					}
					++it;
				}
			}
			catch (CException&)
			{
			}
		}

		bool CGlob::isDirectory(const CPath& path, bool followSymlink)
		{
			CFile f(path);
			bool isDir = false;
			try
			{
				isDir = f.isDirectory();
			}
			catch (CException&)
			{
				return false;
			}
			if (isDir)
			{
				return true;
			}
			else if (followSymlink && f.isLink())
			{
				try
				{
					// Test if link resolves to a directory.
					CDirectoryIterator it(f);
					return true;
				}
				catch (CException&)
				{
				}
			}
			return false;
		}
	}//end of namespace filesys
}//end of namespace JHCPP